import os
import sys
import time
import errno
import fuse
import json
import stat
import math

import StatStruct
import PathData

class JSONFuse(fuse.Fuse):
	"""Gives FUSE functionality to your JSON-object. For further information about
	each method take a look on the FUSE-docs."""

	def __init__(self, *args, **kw):
		"""Constructor, creates the FUSE-instance."""
		fuse.Fuse.__init__(self, *args, **kw)

		self.__jsonFilename=None
		self.__jsonData=PathData.PathData({})
		self.__fileTime=None
		self.__translateBoolean=True
		self.__translateNumbers=True
		self.__translateNone=True
		self.__verbose=False
		self.__encoding="latin1"
	# End def
	
	def getDeEncodedData(self, data):
		"""Decodes the encoded given data. Tries to decode the given data via
		getEncoding()-codec. If that doesn't work it tries to decoded via "latin1",
		otherwise it returns stupidly the given data.

		Parameter:
			string data Data which should be decoded

		Returns:
			string The decoded data.
		"""

		if type(data) not in (str, unicode):
			r=str(data)
		else:
			try:
				r=data.encode(self.getEncoding())
			except:
				try:
					r=data.encode("latin1")
				except:
					r=data
				# End try/except
			# End try/except
		# End if

		return r
	# End def

	def getDeEncodedPath(self, path):
		"""Decodes every coded parts of the given part.

		Example:
			/a/b/c -> a will be decoded, b too, and c also

		Parameter:
			string path Path string, which should be decoded.

		Return:
			string Decoded path-string.
		"""

		parts=path.split(os.sep)
		for k in range(0, len(parts)):
			parts[k]=parts[k].decode(sys.stdin.encoding)
		# End for
		path="%s%s" % (os.sep, os.sep.join(parts[1:]))

		return path
	# End def

	def getattr(self, path):
		"""Returns a stat-structure."""

		if self.__verbose: print "* getattr:", "path:", path

		st=StatStruct.StatStruct()
		st.st_atime=st.st_mtime=st.ctime=self.__getFileTime()

		path=self.getDeEncodedPath(path)

		if path==os.sep or self.__jsonData.has_key(path):
			if path==os.sep or self.__jsonData[path].typeOf() in (PathData.PathData.TYPE_OF_DIR, PathData.PathData.TYPE_OF_LIST):
				st.st_mode=stat.S_IFDIR | 0770
				st.st_nlink=1
				st.st_size=4096
				return st
			elif self.__jsonData[path].typeOf()==PathData.PathData.TYPE_OF_FILE:
				st.st_mode=stat.S_IFREG | 0770
				st.st_nlink=1

				r=self.__jsonData[path].getData()
				r=self.getDeEncodedData(r)
				st.st_size=len(r)

				return st
			else:
				return -errno.ENOENT
			# End if
		else:
			return -errno.ENOENT
		# End if
	# End def

	def readdir(self, path, offset):
		"""Yields the directory-entries."""
		if self.__verbose: print "* readdir: ", "path:", path, "offset:", offset

		path=self.getDeEncodedPath(path)

		if path==os.sep or self.__jsonData.has_key(path):
			dirEntries=[os.curdir, os.pardir]+self.__jsonData[path].keys()
			
			for dirEntry in dirEntries[offset:]:
				s=dirEntry
				if type(s)==unicode:
					s=s.encode(sys.stdout.encoding)
				else:
					s=str(s)
				# End if

				yield fuse.Direntry(s)
			# End for
		# End if
	# End def

	def mythread (self):
		"""mythread, not yet implemented."""
		if self.__verbose: print "* mythread"

		return -errno.ENOSYS
	#  End def

	def chmod ( self, path, mode ):
		"""chmod, not yet implemented."""
		if self.__verbose: print "* chmod:", "path:", path, "mode:", oct(mode)

		return -errno.ENOSYS
	# End def

	def chown ( self, path, uid, gid ):
		"""chown, not yet implemented."""
		if self.__verbose: print "* chown:", "path:", path, "uid:", uid, "gid:", gid

		return -errno.ENOSYS
	# End def

	def fsync ( self, path, isFsyncFile ):
		"""fsync, not yet implemented."""
		if self.__verbose: print "* fsync:", "path:", path, "ifFsyncFile:", isFsyncFile

		return -errno.ENOSYS
	# End def

	def link ( self, targetPath, linkPath ):
		"""link, not yet implemented."""
		if self.__verbose: print "* link:", "targetPath:", targetPath, "linkPath:", linkPath

		return -errno.ENOSYS
	# End def

	def mkdir( self, path, mode ):
		"""Creates a directory.

		Parameter:
			string path The directory-path which should be created.
			integer mode The filesystem-mode which should be set, NOT IMPLEMENTED.
		"""

		if self.__verbose: print "* mkdir:", "path:", path, "mode:", oct(mode)

		path=self.getDeEncodedPath(path)

		try:
			self.__jsonData[path]={}
			self.__save()
			return 0
		except:
			return -errno.ENOSYS
		# End try/except
	#  End def

	def rmdir( self, path ):
		"""Removes/Deletes a directory.

		Parameter:
			string path The directory-path which should be removed/deleted.
		"""

		if self.__verbose: print "* rmdir: ", "path:", path

		path=self.getDeEncodedPath(path)

		if len(self.__jsonData[path])==0:
			del self.__jsonData[path]
			self.__save()
			return 0
		else:
			return -errno.ENOTEMPTY
		# End if
	# End def

	def mknod(self, path, mode, rdev):
		"""Creates a "file", in JSON that means not an object, array or something like this.

		Parameter:
			string path The path-string-named file which should be created.
			integer mode Type of file and permissions, NOT IMPLEMENTED.
			integer rdev Contains a device-id, NOT IMPLEMENTED.
		"""

		if self.__verbose: print "* mknod: ", "path:", path, "mode:", oct(mode), "rdev:", rdev

		path=self.getDeEncodedPath(path)

		try:
			self.__jsonData[path]=""
			self.__save()
			return 0
		except:
			return -errno.ENOSYS
		# End try/except
	# End def

	def readlink( self, path ):
		"""readlink, not yet implemented."""
		if self.__verbose: print "* readlink:", "path:", path

		return -errno.ENOSYS
	# End def

	def rename(self, oldPath, newPath):
		"""Renames/Moves an current/old path to a new name/location.

		Parameter:
			string oldPath Path-String which should be renamed/moved.
			string newPath Path-String where the oldPath should be renamed/moved to.
		"""

		if self.__verbose: print "* rename:", "oldpath:", oldPath, "newPath:", newPath

		oldPath=self.getDeEncodedPath(oldPath)
		newPath=self.getDeEncodedPath(newPath)

		try:
			self.__jsonData[newPath]=self.__jsonData.pop(oldPath)
			self.__save()
			return 0
		except:
			return -errno.ENOSYS
		# End try/except
	# End def

	def statfs( self ):
		"""Returns information about the imaginary filesystem, like block-sizes, available spaces, aso."""

		statvfs=os.statvfs(self.getFilename())
		#rvfs=fuse.StatVfs(f_bsize=statvfs.f_bsize, f_frsize=statvfs.f_bsize, f_blocks=statvfs.f_blocks, f_bfree=statvfs.f_bfree, f_bavail=statvfs.f_bavail, f_files=statvfs.f_files, f_ffree=statvfs.f_ffree, f_namemax=statvfs.f_namemax)
		rvfs=fuse.StatVfs(f_bsize=statvfs.f_bsize, f_frsize=statvfs.f_bsize, f_blocks=statvfs.f_blocks, f_bfree=0, f_bavail=0, f_files=statvfs.f_files, f_ffree=statvfs.f_ffree, f_namemax=statvfs.f_namemax)

		if self.__verbose: print "* statfs"

		# Actually filesize
		blocksFilesize=long(math.ceil(os.path.getsize(self.getFilename())/(statvfs.f_bsize*1.0)))
		rvfs.f_blocks=blocksFilesize

		if self.__verbose: print "* statfs, ", rvfs
		
		return rvfs
	# End def

	def symlink( self, targetPath, linkPath ):
		"""symlink, not yet implemented."""

		if self.__verbose: print "* symlink:", "targetPath:", targetPath, "linkPath:", linkPath

		return -errno.ENOSYS
	# End def

	def truncate( self, path, size ):
		"""Truncates the given path-string(file).

		Parameter:
			string path Path-String which shold be truncated.
			integer size Size-position to which should be truncated.
		"""
		
		if self.__verbose: print "* truncate:", "path:", path, "size:", size

		path=self.getDeEncodedPath(path)

		try:
			self.__jsonData[path]=str(self.__jsonData[path].getData())[:size]
			self.__save()
			return 0
		except:
			return -errno.ENOSYS
		# End try/except
	# End def

	def unlink(self, path):
		"""Removes/deletes a file.
		
		Parameter:
			string path Path-String which should be removed/deleted.
		"""

		if self.__verbose: print "* unlink:", "path:", path

		path=self.getDeEncodedPath(path)

		try:
			del self.__jsonData[path]
			self.__save()
			return 0
		except:
			return -errno.ENOSYS
		# End try/except
	# End def

	def utime(self, path, times):
		"""Sets the times of the json-dir world-data. Every directory/file has the
		same times, atime, mtime, ctime, because json-data is under the score one file.
		
		Parameter:
			string path Path-String of which the times should be set.
			tuple times A tuple of times, simply the first one will be used.
		"""

		if self.__verbose: print "* utime:", "path:", path, "times:", times

		self.__setFileTime(times[0])
		return 0
	# End def

	def read(self, path, size, offset):
		"""Reads/Returns size-bytes from the beginning of the offset-position of the given file.

		Parameter:
			string path Path-String which should be read/returned.
			integer size Size of bytes which should be read/returned.
			integer offset Offset-position from which should be read/returned size bytes.
		"""
		if self.__verbose: print "* read:", "path: ", path, "size:", size, "offset:", offset

		path=self.getDeEncodedPath(path)

		try:
			r=self.__jsonData[path].getData()
			#r=self.getDeEncodedData(r)
			return r[offset:offset+size]
		except:
			return -errno.EIO
		#  End try/except
	# End def

	def open( self, path, flags ):
		"""Opens the internal file-handler.

		Parameter:
			string path Path-String which should be opened.
			integer flags Combination of file-structures-flags, NOT IMPLEMENTED.
		"""
		if self.__verbose: print "* open:", "path:", path, "flags:", flags

		path=self.getDeEncodedPath(path)

		try:
			self.__jsonData[path]=self.__jsonData[path].getData().encode(self.getEncoding())
		except:
			pass
		# End try/except

		return 0
	# End def

	def write(self, path, buf, offset):
		"""Writes buf-data to the offset-position of the path-file.

		Parameter:
			string path Path-String to which should be written.
			bytes buf Raw-data which should be written to the offset-position.
			integer offset Offset-position to which should be written.
		"""

		if self.__verbose: print "* write:", "path:", path, "buf:", "<buf...>", "offset:", offset

		path=self.getDeEncodedPath(path)

		try:
			bufSave=buf

			# Assign newly given buf-data
			pathData=self.__jsonData[path].getData()
			self.__jsonData[path]=pathData[0:offset]+bufSave+pathData[offset+len(buf):]

			# Return written length
			return len(buf)
		except:
			return -errno.ENOSYS
		# End try/except
	#  End def

	def release(self, path, flags):
		"""Closes the internal file-handler.

		Transforms the data behind the path. Take a look on "Translate Boolean/
		Numbers/None".

		Parameter:
			string path Path-String which should be closed.
			integer flags Combination of file-structures-flags, NOT IMPLEMENTED.
		"""

		if self.__verbose: print "* release:", "path:", path, "flags:", flags

		path=self.getDeEncodedPath(path)
		buf=self.__jsonData[path].getData()
		bufSave=buf

		# Try to decode text-data ...
		try:
			bufSave=buf.decode(self.getEncoding())
		# ... if that does'nt work, try latin1 to decode
		except:
			try:
				bufSave=buf.decode("latin1")
			except:
				pass
			# End try/except
		# End try/except

		# Translate boolean-strings to boolean-values
		if self.getTranslateBoolean() and type(bufSave) in (str, unicode) and bufSave.strip().lower() in ("true", "false"):
			try:
				bufSave=bool(bufSave)
			except:
				pass
			# End try/except
		# End if

		# Translate number/float-strings to number/float-valuews
		if self.getTranslateNumbers() and type(bufSave) in (str, unicode):
			try:
				bufSave=long(bufSave)
			except:
				try:
					bufSave=float(bufSave)
				except:
					pass
				# End try/except
			# End try/except
		# End if

		# Tanslate None/Null-strings to none-values
		if self.getTranslateNone() and type(bufSave) in (str, unicode) and bufSave.strip().lower() in ("none", "null"):
			bufSave=None
		# End def

		self.__jsonData[path]=self.__jsonData[path].getData().decode(self.getEncoding())

		# Save data to 
		self.__save()

		return 0
	# End def

	def __setFileTime(self, newFileTime):
		"""Internal method, called via fuse-utime-method to set the file-a/m/c-times.

		Parameter:
			unix-epoch-time, integer newFileTime The unix-epoch-time for every directory/file.
		"""

		if self.__verbose: print "* __setFileTime:", "newFileTime:", newFileTime

		self.__fileTime=newFileTime
	# End def

	def __getFileTime(self):
		"""Returns the currently set directory/file-time."""

		if self.__verbose: print "* __getFileTime: ", self.__fileTime

		return self.__fileTime
	# End def

	def __load(self):
		if self.__verbose: print "* __load"

		if self.getFilename()!=None:
			if not os.path.exists(self.getFilename()):
				self.__jsonData.setData({})
				self.__save()
				self.__setFileTime(time.time())
				return 0
			# End if

			try:
				js=json.load(open(self.getFilename(), "rb"), encoding=self.getEncoding())
				self.__jsonData.setData(js)

				if self.__verbose: print "* Loaded from", self.getFilename()
				self.__setFileTime(os.path.getmtime(self.getFilename()))
				return 0
			except:
				if self.__verbose: print "* Cannot load from json-file (%s)." % self.getFilename()
				sys.exit(1);
			# End try/except
		# End if
	# End def

	def __save(self):
		if self.__verbose: print "* __save"

		if self.getFilename()!=None:
			try:
				json.dump(self.__jsonData.getData(), open(self.getFilename(), "wb"), indent=self.getIndentation(), encoding=self.getEncoding())

				if self.__verbose: print "* Saved to", self.getFilename()
				self.__setFileTime(os.path.getmtime(self.getFilename()))
				return 0
			except:
				if self.__verbose: print "* Cannot write to json-file (%s)." % self.getFilename()
				sys.exit(1);
			# End try/except
		# End if
	# End def

	def setFilename(self, jsonFilename):
		if self.__verbose: print "* setFilename:", "jsonFilename:", jsonFilename

		if jsonFilename!=None and type(jsonFilename)!=str: raise TypeError

		self.__jsonFilename=jsonFilename
		self.__load()
	# End def

	def getFilename(self):
		if self.__verbose: print "* getFilename", self.__jsonFilename
		return self.__jsonFilename
	# End def

	def setTranslateBoolean(self, translate):
		"""Defines how boolean-strings should be transformed.

		Example:
			translate=True
			"true" => boolean, true
			"false" => boolean, false

			translate=False
			"true" => string, "true"
			"false" => string, "false"

		Parameter:
			bool translate True to translate boolean-strings to boolean-values, otherwise false.
		"""

		if self.__verbose: print "* setTranslateBoolean:", "translate:", translate
		if type(translate)!=bool: raise TypeError
		self.__translateBoolean=translate
	# End def

	def getTranslateBoolean(self):
		"""Returns the currently set translate-boolean-setting."""

		if self.__verbose: print "* getTranslateBoolean", self.__translateBoolean
		return self.__translateBoolean
	# End def

	def setTranslateNumbers(self, translate):
		"""Defines how number-strings should be transformed.
		
		Example:
			translate=True
			"123" => integer, 123
			"123.45" => float, 123.45

			translate=False
			"123" => string, "123"
			"123.45" => string, "123.45"

		Parameter:
			bool translate True to translate number-string to number-values, otherwise false.
		"""
			
		if self.__verbose: print "* setTranslateNumbers:", "translate:", translate
		if type(translate)!=bool: raise TypeError
		self.__translateNumbers=translate
	# End def

	def getTranslateNumbers(self):
		"""Returns the currently set translate-numbers-setting."""

		if self.__verbose: print "* getTranslateNumbers", self.__translateNumbers
		return self.__translateNumbers
	# End def

	def setTranslateNone(self, translate):
		"""Defines how none-strings should be transformed.

		Example:
			translate=True
			"none" => null, null
			"null" => null, null

			translate=False
			"none" => string, "none"
			"null" => string, "null"

		Parameter:
			bool translate True to translate none-strings to none-vallues, otherwise false.
		"""
		if self.__verbose: print "* setTranslateNone:", "translate:", translate
		if type(translate)!=bool: raise TypeError
		self.__translateNone=translate
	# End def

	def getTranslateNone(self):
		"""Returns the currently set translate-none-setting."""

		if self.__verbose: print "* getTranslateNone", self.__translateNone
		return self.__translateNone
	# End def

	def setVerbosity(self, verbose):
		"""Sets the verbosity for debugging-output. Should/can only be used
		with FUSE-option -f or -d.

		Parameter:
			bool verbose True to enable verbosity, false to be quiet.
		"""

		if verbose: print "* setVerbosity:", "verbose:", verbose
		if type(verbose)!=bool: raise TypeError
		self.__verbose=verbose
	# End def

	def getVerbosity(self):
		"""Returns the currently set verbosity-setting."""

		if self.__verbose: print "* getVerbosity:", self.__verbose
		return self.__verbose
	# End def

	def setIndentation(self, indentation):
		"""Sets the indentation-level, number of spaces, the JSON-data should be saved with.

		Example:
			indentation=None
			{"a": 1, "b": "b-value"}

			indendation=2
			{
			  "a": 1,
			  "b": "b-value"
			}

		Parameter:
			integer indentation Number of spaces to indent the json-file with, default no indendation.
		"""

		if self.__verbose: print "* setIndentation:", "indentation:", indentation
		if indentation!=None and type(indentation)!=int: raise TypeError
		self.__indendation=indentation
	# End def

	def getIndentation(self):
		"""Returns the currently set indentation-level."""

		if self.__indendation: print "* getIndentation:", self.__indendation
		return self.__indendation
	# End def

	def setEncoding(self, encoding):
		"""Defines how your json-file is encoded and should be decoded on saving-process.
		Actually it's pretty hard to combine raw-text-data, utf-8/latin encoded text-data,
		binary-stored jpeg-data in a json-environment, so it's recommended to take the
		default, latin1.

		Parameter:
			string encoding The encoding your json-file is load and saved with.
		"""

		if self.__verbose: print "* setEncoding:", "encoding:", encoding
		if type(encoding)!=str: raise TypeError
		self.__encoding=encoding
	# End def

	def getEncoding(self):
		"""Returns the currently set encoding-type."""

		if self.__verbose: print "* getEncoding:", self.__encoding
		return self.__encoding
	# End def

# End class
